package sapling.main;

import sapling.logic.algorithm.pcb.dispatcher.Dispatcher;
import sapling.logic.algorithm.pcb.dispatcher.FCFSDispatcher;
import sapling.logic.algorithm.pcb.dispatcher.PriorityDispatcher;
import sapling.logic.algorithm.pcb.dispatcher.TSRDispatcher;
import sapling.logic.algorithm.pcb.queue.FCFSQueue;
import sapling.logic.algorithm.pcb.queue.PriorityQueue;
import sapling.logic.algorithm.pcb.queue.Queue;
import sapling.logic.algorithm.pcb.queue.TSRQueue;
import sapling.logic.object.cpu.CPU;
import sapling.logic.object.pcb.ProcessControlBlock;
import sapling.logic.object.pcb.SceneInfo;
import sapling.logic.object.pcb.attr.Priority;
import sapling.util.MyStrings;
import sapling.util.ReadConfig;

public class OSEnvironment {

	private Dispatcher dispatcher;
	private CPU cpu;

	private Queue pcbQueue;
	private ProcessControlBlock pcbRunning;

	private static OSEnvironment os;

	public static OSEnvironment getInstance() {
		if (os == null) {
			os = new OSEnvironment();
		}
		return os;
	}


	private OSEnvironment() {
		this.cpu = CPU.getInstance();
		this.pcbRunning = null;
		initDispatcher();
	}


	private void initDispatcher() {
		switch (ReadConfig.DISPATCHER) {
			case MyStrings.FCFS:
				this.dispatcher = new FCFSDispatcher(this);
				this.pcbQueue = new FCFSQueue();
				break;
			case MyStrings.PRIORITY:
				this.dispatcher = new PriorityDispatcher(this);
				this.pcbQueue = new PriorityQueue();
				break;
			case MyStrings.TSR:
				this.dispatcher = new TSRDispatcher(this);
				this.pcbQueue = new TSRQueue();
				break;
			default:
				break;
		}
	}


	public synchronized boolean create(String tag, int liveLong, int priority) {
		ProcessControlBlock pcb = new ProcessControlBlock(tag, priority, liveLong);
		this.pcbQueue.enqueue(pcb);
		if (pcbRunning == null) {
			this.run(this.pcbQueue.dequeue());
		}
		return true;
	}

	private void run(ProcessControlBlock pcb) {
		if (this.pcbRunning != null) {
			this.saveSceneInfo(pcbRunning.getSceneInfo());
		}
		if (pcb == null) {
			this.pcbRunning = null;
			return;
		}
		this.recoverSceneInfo(pcb.getSceneInfo());
		this.pcbRunning = pcb;
	}

	public synchronized boolean change() {
		return this.dispatcher.switchPCB();
	}

	public void setPcbRunning(ProcessControlBlock pcbRunning) {
		this.run(pcbRunning);
	}

	public synchronized void kill(ProcessControlBlock pcb) {
		pcb.setPriority(Priority.NO_LEVEL);
		pcb.setLiveLong(0);
		pcb.release();
		if (pcb == this.pcbRunning) {
			this.change();
		}
	}

	private void saveSceneInfo(SceneInfo sceneInfo) {
		sceneInfo.setControlRegister(cpu.getControlRegister());
		sceneInfo.setGeneralRegister(cpu.getGeneralRegister());
		sceneInfo.setPswRegister(cpu.getPswRegister());
		sceneInfo.setSysStackPointer(cpu.getSysStackPointer());
		sceneInfo.setUsrStackPointer(cpu.getUsrStackPointer());
	}

	private void recoverSceneInfo(SceneInfo sceneInfo) {
		cpu.setControlRegister(sceneInfo.getControlRegister());
		cpu.setGeneralRegister(sceneInfo.getGeneralRegister());
		cpu.setPswRegister(sceneInfo.getPswRegister());
		cpu.setSysStackPointer(sceneInfo.getSysStackPointer());
		cpu.setUsrStackPointer(sceneInfo.getUsrStackPointer());
	}


	public void restart() {
		this.pcbRunning = null;
		this.pcbQueue.removeAll();
		this.initDispatcher();
		ProcessControlBlock.initID();
	}


	public Queue getPcbQueue() {
		return pcbQueue;
	}

	public ProcessControlBlock getPcbRunning() {
		return pcbRunning;
	}
}
